package org.chen.spring.core.io;

import org.chen.spring.core.annotation.ConfigurationProperties;
import org.chen.spring.core.constant.PropertiesConstant;
import org.chen.spring.core.factory.BeanFactory;
import org.yaml.snakeyaml.Yaml;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.lang.reflect.Field;
import java.net.URL;
import java.util.*;

/**
 * Yml配置文件工厂
 *
 * @author Chen
 * @since 2023/6/7 14:36
 */
public class YmlResourceLoader implements ResourceLoader {

    @Override
    public void getResource(URL path, List<String> packageNameList) throws FileNotFoundException {
        BufferedReader br = new BufferedReader(new FileReader(path.getPath()));
        // 2. 创建yaml工具类
        Yaml yaml = new Yaml();
        // 3. 使用yaml工具类加载 流
        // 返回的字符形式非常类似于JSON, 但又不是JSON, 因为返回的字符之间使用的是 = 号连接的
        // 可以直接转换为Map 进行处理, key 的类型是 字符串, value的类型为Object
        Map<String, Object> yamlMap = yaml.load(br);
        ClassLoader classLoader = this.getClass().getClassLoader();
        for (String bootPackage : packageNameList) {
            URL resource = classLoader.getResource(bootPackage.replace(".", "/"));
            String configPath = null;
            if (resource != null) {
                configPath = resource.getFile();
            }
            try {
                scanConfig(bootPackage, configPath, yamlMap);
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        }


    }

    private static void scanConfig(String configPath, String path, Map<String, Object> yamlMap) throws Exception {
        File file = new File(path);

        if (file.isDirectory()) {
            for (File listFile : Objects.requireNonNull(file.listFiles())) {
                String filePath = listFile.getAbsolutePath();
                if (listFile.isDirectory()) {
                    scanConfig(configPath, listFile.getPath(), yamlMap);
                }
                if (filePath.endsWith(".class")) {
                    String prefix = configPath.substring(0, configPath.indexOf("."));
                    // com\chen\service\UserService
                    String className = filePath.substring(filePath.indexOf(prefix), filePath.indexOf(".class"));
                    className = className.replace("\\", ".");
                    Class<?> aClass = Class.forName(className);
                    if (aClass.isAnnotationPresent(ConfigurationProperties.class)) {
                        Object instance = aClass.getDeclaredConstructor().newInstance();
                        ConfigurationProperties annotation = aClass.getAnnotation(ConfigurationProperties.class);
                        String startName = annotation.prefix();
                        if (startName.startsWith(PropertiesConstant.PREFIX_NAME)) {
                            startName = "chen";
                        }
                        setField(aClass, instance, yamlMap, startName);
                        BeanFactory.registerBean(aClass.getSimpleName(), instance);
                    }

                }
            }
        }
    }

    /**
     * 设置配置Bean属性
     * <p>
     * 根据配置Bean属性前缀，获取yaml配置文件中的属性值，设置到配置Bean中
     * 这里采用递归的方式，获取配置Bean中的属性值，如果是Object类型则设置属性
     * 如果为Map类型，则继续递归获取属性值
     * </p>
     *
     * @param aClass    配置Bean
     * @param instance  配置Bean实例
     * @param yamlMap   yaml配置文件参数
     * @param startName 配置Bean属性前缀
     */
    private static void setField(Class<?> aClass, Object instance, Map<String, Object> yamlMap, String startName) throws NoSuchFieldException, IllegalAccessException {
        Map<String, Object> propertiesMap = (Map<String, Object>) yamlMap.get(startName);
        for (Map.Entry<String, Object> listEntry : propertiesMap.entrySet()) {
            String key = listEntry.getKey();
            Object value = listEntry.getValue();
            if (value instanceof Map<?, ?>) {
                setField(aClass, instance, propertiesMap, key);
                continue;
            }
            Field field = aClass.getDeclaredField(listEntry.getKey());
            field.setAccessible(true);
            field.set(instance, listEntry.getValue());
        }
    }
}
